<script lang="ts" setup>
import {useRouter} from 'vue-router'
import {computed, h, nextTick, onBeforeMount, onBeforeUnmount, onUnmounted, reactive, ref, watch} from "vue";
import {ElMessage, ElMessageBox, InputInstance, TableInstance} from 'element-plus'
import {ArrowRight, Check, Close, Search} from "@element-plus/icons-vue";
import {service, fileIcon, ProgressEventSourceWrapper, sizeFormat} from "@/utils";
import {PrimaryKey, FileTableProps, DirTreeProps, HeaderProps, FileInterface} from './interface'
import ContextMenu, {ContextmenuProps, PasteProps} from '@/components/right-click-menu/index.vue'
import Uploader from '@/components/file-upload/index.vue'
import LeftRightLayout from "@/views/file-zilla/icons/left-right-layout.vue";
import Backward from "@/views/file-zilla/icons/backward.vue";
import Forward from "@/views/file-zilla/icons/forward.vue";
import Refresh from "@/views/file-zilla/icons/refresh.vue";

const props = withDefaults(defineProps<{ sessionId?: string, primaryKey?: PrimaryKey }>(), {
    sessionId: '',
    primaryKey: 'fullPath'
})

const router = useRouter();

const session = ref<string>(props.sessionId);

// 路径导航
const pathRef = ref<InputInstance>();
const searchRef = ref<InputInstance>();
const header = reactive<HeaderProps>({
    isDirTree: true,
    isPath: false,
    isSearch: false,
    home: '',
    path: undefined,
    keyword: '',
    searchSource: undefined,
    breadcrumbs: [],
    history: {
        stack: [],
        index: -1,
    },
    onChangeDirTree: () => {
        header.isDirTree = !header.isDirTree;
    },
    onInitPath: (path: string | undefined, homePath = header.home) => {
        if (!homePath) {
            console.error("根目录信息不能为空")
            return;
        }
        if (path) {
            if (path !== header.history.stack[header.history.index]) {
                header.history.stack.splice(header.history.index + 1);
                header.history.stack.push(path);
                header.history.index = header.history.stack.length - 1;
            }

            if (path.indexOf(homePath) === 0) {
                header.breadcrumbs = [{path: homePath, label: '用户'}];
                // 根目录路径不展示
                const ls = path.replace(homePath, '').split('/');
                for (let i = 1; i < ls.length; i++) {
                    header.breadcrumbs.push({path: header.breadcrumbs[i - 1].path + '/' + ls[i], label: ls[i]})
                }
            } else {
                header.breadcrumbs = [{path: path, label: path}];
            }
        } else {
            header.breadcrumbs = [];
        }
    },
    onSearch: () => {
        if (header.keyword) {
            header.searchSource = fileTable.dataSource.filter(file => file.filename.toLowerCase().indexOf(header.keyword.toLowerCase()) !== -1);
        } else {
            header.searchSource = undefined;
        }
    },
    onClickPathItem: (key: string) => {
        dirTreeRef.value.setCurrentKey(key);
    },
    onPathInputStatus: (status: boolean) => {
        header.isPath = status;
        if (header.isPath) {
            header.path = header.breadcrumbs.last().path;
            nextTick(() => {
                pathRef.value?.focus();
                pathRef.value?.select();
            })
        }
    },
    onSearchInputStatus: (status: boolean | undefined) => {
        header.isSearch = status;
        header.keyword = '';
        header.searchSource = undefined;
    },
    onSearchInputFocus: () => {
        searchRef.value?.focus()
    },
    onNavigate: (direction: 'backward' | 'forward') => {
        if (direction === 'backward') header.history.index--
        if (direction === 'forward') header.history.index++
        dirTreeRef.value.setCurrentKey(header.history.stack[header.history.index])
    }
})

// 目录树
const dirTreeRef = ref();
const dirTree = reactive<DirTreeProps>({
    loading: false,
    data: [],
    dirProps: {
        label: 'filename',
    },
    onCurrentChange: async (data: any, node: any) => {
        if (data) {
            const files = await fileTable.onLoadData(data.fullPath) as Array<FileInterface>;
            await nextTick(() => {
                // 存在新增目录则添加目录树节点
                let filenames = node.data?.children?.map((item: any) => item.filename) || [];
                files
                        .filter(item => item.directory && !filenames.includes(item.filename))
                        .forEach(item => dirTreeRef.value?.append(item, data[props.primaryKey]))
                // 存在删除目录则移除目录树节点
                filenames = files.map((item: any) => item.filename) || [];
                node.data.children
                        ?.filter((item: any) => item.directory && !filenames.includes(item.filename))
                        ?.forEach((item: any) => dirTreeRef.value?.remove(item[props.primaryKey]))
                // 设置节点状态
                node.expanded = true
                node.loaded = true
            })
        }
    }
})


// 文件展示表格
const editRowRef = ref<InputInstance>();
const fileTableRef = ref<TableInstance>();
const uploaderRef = ref<InstanceType<typeof Uploader>>();
const wrapper = new ProgressEventSourceWrapper('/sftp/progress');
const fileTable = reactive<FileTableProps>({
    loading: false,
    editRow: {
        filename: '',
        fullPath: ''
    },
    dataSource: [],
    selections: [],
    selectionKeys: [],
    onLoadData: async (ph = header.breadcrumbs.last()?.path) => {
        fileTable.loading = true;
        fileTableRef.value?.clearSelection(); //刷新时已勾选的复选框会有残影
        return await service.get(`/sftp/ls/${session.value}`, {path: ph}).then(({code, data, path}): Array<any> | undefined => {
            if (code === 200) {
                // 加载数据
                header.searchSource = undefined;
                fileTable.dataSource = data.rows || [];
                //每次根据后端返回的路径信息重新初始化
                header.onInitPath(path);
                // 选中指定文件
                if (fileTable.selectionKeys.length > 0) {
                    nextTick(() => {
                        let obj = fileTable.dataSource.find(item => fileTable.selectionKeys.includes(item[props.primaryKey]));
                        if (obj) {
                            fileTableRef.value?.setCurrentRow(obj);
                            fileTableRef.value?.toggleRowSelection(obj, undefined, false);
                        }
                    })
                }
            } else {
                fileTable.dataSource = []
            }
            return fileTable.dataSource;
        }).finally(() => {
            fileTable.loading = false;
        });
    },
    // 重新加载目录树和表格数据
    onReloadData: () => {
        dirTreeRef.value.setCurrentKey(undefined);
        dirTreeRef.value.setCurrentKey(header.breadcrumbs.last().path);
    },
    // 行的点击事件
    onRowClick: (row?: any) => {
        // 存在复选框时，点击一行则默认选中当前行
        fileTableRef.value?.clearSelection();
        if (!!row) {
            fileTableRef.value?.toggleRowSelection(row, true, false);
        }
    },
    // 行的双击事件
    onRowDblClick: (row: any) => {
        row.directory && dirTreeRef.value.setCurrentKey(row[props.primaryKey]);
    },
    // 选中行发生改变
    onSelectionChange: (rows: any) => {
        fileTable.selections = rows;
    },
    // 右键菜单
    onRowContextmenu: (row: any, column: any, event: MouseEvent) => {
        event.preventDefault();
        event.stopPropagation();
        contextmenu.onOpen(event, row);
    },
    // 设置选择时的样式
    onRowClassName(data: { row: never, rowIndex: number }) {
        if (fileTable.selections.includes(data.row)) {
            return 'current-row'
        }
        return ''
    },
    // 新建文件夹
    onCreateFolder: (path: string) => {
        service.post(`/sftp/mkdir/${session.value}`, {path: path}).then(({code, data}) => {
            if (code === 200) {
                fileTable.selectionKeys = [data[props.primaryKey]];
                fileTable.onReloadData();
            }
        })
    },
    // 删除文件
    onDelete: (path: string, rows: any[]) => {
        ElMessageBox.confirm('确定删除已选择文件?', '删除提示', {
            confirmButtonText: '删除',
            cancelButtonText: '取消',
            type: 'warning',
        }).then(() => {
            service.delete(`/sftp/rmf/${session.value}`, {path: path, filenames: rows.map(item => item.filename)}).then(({code}) => {
                if (code === 200) {
                    fileTable.onReloadData();
                }
            })
        }).catch(() => ({}));
    },
    // 编辑文件名
    onEditFilename: (row: any) => {
        fileTable.editRow = row;
        nextTick(() => editRowRef.value?.focus())
    },
    // 重命名
    onRename: (row?: any) => {
        if (fileTable.editRow.filename) {
            if (row && row.filename !== fileTable.editRow.filename) {
                service.put(`/sftp/rename/${session.value}`, {path: row.path, oldFilename: row.filename, newFilename: fileTable.editRow.filename}).then(({code, data}) => {
                    if (code === 200) {
                        row.filename = data.filename;
                        row.name = data.name;
                        row.extension = data.extension;
                        fileTable.editRow[props.primaryKey] = undefined;
                        // 更新目录信息
                        if (data.directory) {
                            dirTreeRef.value.updateKeyChildren(data[props.primaryKey], data);
                        }
                    }
                })
            } else {
                fileTable.editRow[props.primaryKey] = undefined;
            }
        } else {
            fileTable.editRow[props.primaryKey] = undefined;
        }
    },
    // 剪切/复制后粘帖
    onPaste: (path: string, paste: PasteProps) => {
        const type = paste.type === 'move' ? '移动' : '复制';
        ElMessageBox({
            title: '提示',
            message: h('p', {style: 'width: calc(var(--el-messagebox-width) - var(--el-messagebox-padding-primary) * 2); display: flex;align-items: center; white-space: nowrap;'}, [
                h('span', null, `确定${type}至`),
                h('span', {style: 'text-decoration: underline; text-decoration-line: grammar-error; color: teal;  width: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;', title: path}, `${path}`),
                h('span', null, `?`),
            ]),
            draggable: true,
            confirmButtonText: type,
            cancelButtonText: '取消',
            beforeClose: (action, instance, done) => {
                if (action === 'confirm') {
                    instance.confirmButtonLoading = true
                    instance.confirmButtonText = 'Loading...'
                    const filenames = paste.data.map(item => item.filename);
                    if (paste.type === 'move') {
                        service.put(`/sftp/mv/${session.value}`, {source: paste.path, target: path, filenames: filenames}).then(({code, data}) => {
                            //todo 可以优化本地增删数据
                            if (code === 200) {
                                paste.data.forEach(item => item.directory && dirTreeRef.value.remove(item[props.primaryKey]));
                                paste.data = [];
                                // 重新加载表格数据
                                fileTable.onReloadData();
                            }
                        }).finally(() => {
                            done();
                            instance.confirmButtonLoading = false;
                        })
                    } else {
                        service.put(`/sftp/cp/${session.value}`, {source: paste.path, target: path, filenames: filenames}).then(({code, data}) => {
                            //todo 可以优化本地增删数据
                            if (code === 200) {
                                paste.data = [];
                                // 重新加载表格数据
                                fileTable.onReloadData();
                            }
                        }).finally(() => {
                            done();
                            instance.confirmButtonLoading = false;
                        })
                    }
                } else {
                    done()
                }
            },
        }).then((action) => {

        })
    },
    // 上传
    onUpload: (path = header.breadcrumbs.last().path) => {
        uploaderRef.value?.show({service: 'sftp', from: session.value, target: path})
    },
    // 下载
    onDownload: async (path: string, rows: any[]) => {
        wrapper.onOpen({id: session.value})
                .onMessage((message: string) => {
                    if (message.indexOf("{") === 0) {
                        const data = JSON.parse(message);
                        downloadProgress.filename = data.filename;
                        downloadProgress.percentage = data.progress;
                        downloadProgress.remaining = data.total - data.count;
                        downloadProgress.total = data.total;
                    } else {
                        downloadProgress.filename = message;
                    }
                })
                .onStart(() => {
                    const controller = new AbortController()
                    service.download(`/sftp/download/task/${session.value}`, {path: path, filenames: rows.map(item => item.filename)}, {signal: controller.signal, timeout: 0})
                    downloadProgress.onCancel = () => controller.abort();
                    downloadProgress.visible = true
                })
                .onClose(() => {
                    downloadProgress.visible = false
                    nextTick(() => {
                        downloadProgress.filename = ''
                        downloadProgress.percentage = 0;
                        downloadProgress.remaining = 0;
                        downloadProgress.total = 0;
                        downloadProgress.controller = undefined;
                    })
                });
    }
})
const tableData = computed(() => header.searchSource || fileTable.dataSource)
const statistic = reactive({
    count: 0,
    details: ''
})
watch(() => tableData.value, (v) => {
    statistic.count = v.length;
    let fn = 0, dn = 0, size = 0;
    Array.from(v).forEach(item => {
        if (item.directory) {
            fn ++;
        } else {
            dn ++;
            size += item.size;
        }
    })

    statistic.details = `${fn}个目录，${dn}个文件。 大小总计：${size.toLocaleString()} 字节（${sizeFormat(size)}）`
})


// 右键菜单
const contextmenu = reactive<ContextmenuProps>({
    data: {
        position: [0, 0],
        parameter: []
    },
    paste: {
        type: 'copy',
        path: '',
        data: []
    },
    menus: [
        {id: 'open', label: '打开'},
        {id: 'refresh', label: '重新加载', icon: 'icon-refresh'},
        {id: 'folder', label: '新建文件夹', icon: 'icon-folder'},
        {separator: true},
        {id: 'upload', label: '上传', icon: 'icon-upload-filled'},
        {id: 'download', label: '下载', icon: 'icon-download'},
        {separator: true},
        {id: 'move', label: '移动', icon: 'icon-move'},
        {id: 'copy', label: '复制', icon: 'icon-copy'},
        {id: 'paste', label: '粘贴', hidden: true},
        {id: 'cancel_paste', label: '取消粘贴', hidden: true},
        {separator: true},
        {id: 'delete', label: '删除', icon: 'icon-delete'},
        {id: 'rename', label: '重命名', icon: 'icon-edit'},
    ],
    // 打开菜单前触发事件
    onBeforeOpen: (parameter: any[], menus: Array<any>) => {
        menus.forEach((item: any) => {
            switch (item.id) {
                case 'open':
                    item.disabled = !(parameter.length === 1 && parameter[0].directory)
                    break;
                case 'download':
                    item.disabled = parameter.length === 0
                    break;
                case 'move':
                    item.disabled = parameter.length === 0
                    break
                case 'copy':
                    item.disabled = parameter.length === 0
                    break;
                case 'paste':
                case 'cancel_paste':
                    item.hidden = contextmenu.paste?.data.length === 0
                    if (contextmenu.paste && contextmenu.paste.data.length > 0 && contextmenu.paste.type === 'move') {
                        const currentPath = header.breadcrumbs.last().path;
                        // 不能在当前目录移动；不能在被移动目录下移动
                        item.disabled = currentPath === contextmenu.paste.path || contextmenu.paste.data.some(item => item.directory && (currentPath + '/').indexOf(item.fullPath + '/') === 0);
                    } else {
                        item.disabled = false;
                    }
                    break;
                case 'delete':
                    item.disabled = parameter.length === 0
                    break;
                case 'rename':
                    item.disabled = parameter.length !== 1
                    break;
            }
        })
        return true;
    },
    // 打开菜单
    onOpen: (event: MouseEvent, row?: any) => {
        fileTableRef.value?.setCurrentRow(row);
        if (!!row) {
            // 文件资源-右键在非选中行上点击则取消已选中的所有行，同时选中当前行
            let rs = fileTable.selections?.filter((o: any) => o[props.primaryKey] === row[props.primaryKey]);
            if (rs.length === 0) {
                fileTableRef.value?.clearSelection();
                fileTableRef.value?.toggleRowSelection(row, true, false);
                contextmenu.data.parameter = [row]
            } else {
                contextmenu.data.parameter = fileTable.selections
            }
        } else {
            fileTableRef.value?.clearSelection();
            contextmenu.data.parameter = []
        }
        nextTick(() => contextmenu.data.position = [event.clientX, event.clientY])
    },
    // 菜单项点击事件
    onClick: (menu: any, data: any[]) => {
        switch (menu.id) {
            case 'open':
                dirTreeRef.value.setCurrentKey(data[0][props.primaryKey]);
                break;
            case 'refresh':
                fileTable.onReloadData();
                break;
            case 'folder':
                fileTable.onCreateFolder(header.breadcrumbs.last().path);
                break;
            case 'upload':
                fileTable.onUpload();
                break;
            case 'download':
                fileTable.onDownload(header.breadcrumbs.last().path, data)
                break;
            case 'move':
            case 'copy':
                if (contextmenu.paste) {
                    contextmenu.paste.type = menu.id;
                    contextmenu.paste.data = data;
                    contextmenu.paste.path = header.breadcrumbs.last().path;
                }
                break;
            case 'paste':
                if (contextmenu.paste) {
                    fileTable.onPaste(header.breadcrumbs.last().path, contextmenu.paste)
                }
                break;
            case 'cancel_paste':
                if (contextmenu.paste) {
                    contextmenu.paste.data = [];
                }
                break;
            case 'delete':
                fileTable.onDelete(header.breadcrumbs.last().path, data);
                break;
            case 'rename':
                fileTable.onEditFilename({...data[0]});
                break;
        }
    }
})

// 下载进度
const downloadProgress = reactive({
    visible: false,
    expanded: false, // 控制详细信息的展开
    percentage: 0,
    filename: '',
    remaining: 0,
    total: 0,
    controller: undefined,
    onCancel: () => {}
})

// 初始化用户目录，并加载数据
onBeforeMount(() => {
    let {query, meta} = router.currentRoute.value;
    if (query.uid) {
        session.value = query.uid as string;
    } else {
        window.document.title = `${meta.title}`;
    }

    if (session.value) {
        dirTree.loading = true
        service.get(`/sftp/home/${session.value}`).then(({code, data, host, path}) => {
            if (code === 200 || !Array.isArray(data) || data.length === 0) {
                window.document.title = `${host}-${meta.title}`;
                for (let i = 0; i < data.length; i++) {
                    data[i].children = []; // 初始化当前节点的 children 属性
                    if (i > 0) {
                        data[i - 1].children.push(data[i]);
                    }
                }
                // 加载根目录数据
                dirTree.data.push(data[0]);
                // 初始化路径信息
                if (!header.home) {
                    // 首次连接服务器需要保留根目录路径
                    header.home = path;
                }
                //初始化根目录
                header.onInitPath(path);
            } else {
                dirTree.data = [];
                header.home = '';
                header.onInitPath(undefined);
            }
            return path;
        }).then((path: string | undefined) => {
            if (path) {
                // 设置默认路径后会自动触发el-tree事件current-change进行初始化
                nextTick(() => dirTreeRef.value.setCurrentKey(path))
            }
        }).finally(() => dirTree.loading = false)
    } else {
        ElMessage.error('会话ID无效')
    }
})

// 左右拖拽改变布局大小
const isResizing = ref(false);
const asideWidth = 300;
let animationFrameId: number | null = null;
const onLRResize = (e: MouseEvent) => {
    isResizing.value = true;
    const startX = e.clientX;

    const asideEl = document.getElementById('dir_tree_aside') as HTMLElement;
    const startWidth = (asideEl.clientWidth + 1) || asideWidth;

    const onMouseMove = (e: MouseEvent) => {
        if (!isResizing.value) return;

        // 使用节流处理
        if (!animationFrameId) {
            animationFrameId = requestAnimationFrame(() => {
                // 直接操作DOM避免响应式延迟
                asideEl.style.width = `${Math.max(300, Math.min(720, startWidth + e.clientX - startX))}px`;
                animationFrameId = null;
            });
        }
    };

    const onMouseUp = () => {
        isResizing.value = false;
        document.removeEventListener('mousemove', onMouseMove);
        document.removeEventListener('mouseup', onMouseUp);

        if (animationFrameId) {
            cancelAnimationFrame(animationFrameId);
            animationFrameId = null;
        }
    };

    document.addEventListener('mousemove', onMouseMove);
    document.addEventListener('mouseup', onMouseUp);
};
const onLRResizeDblClick = () => {
    const asideEl = document.getElementById('dir_tree_aside') as HTMLElement
    if (asideEl) {
        asideEl.style.width = `${asideWidth}px`;
    }
}

// 设置表格骨架屏数据量
const num = (window.innerHeight - 130) / 40;
const skeletonData = ref([])
for (let i =0; i < num -1; i++) {
    skeletonData.value.push({} as never)
}
</script>

<template>
    <el-container class="file-container">
        <el-header>
            <div class="header-left">
                <left-right-layout :full="!header.isDirTree" @click="header.onChangeDirTree()"/>
                <el-divider direction="vertical"/>
                <backward :disabled="header.history.index <= 0" @click="header.onNavigate('backward')"/>
                <forward :disabled="header.history.index >= header.history.stack.length - 1" @click="header.onNavigate('forward')"/>
                <el-divider direction="vertical"/>
                <refresh @click="fileTable.onReloadData()"/>
            </div>
            <div class="header-center">
                <el-input v-if="header.isPath" ref="pathRef" v-model="header.path" style="width: 100%;height: 100%" @blur="header.onPathInputStatus(false)"/>
                <el-breadcrumb v-else :separator-icon="ArrowRight" class="header-center__path" @click.prevent="header.onPathInputStatus(true)">
                    <el-breadcrumb-item v-for="(item, index) in header.breadcrumbs" :key="index" @click.stop="header.onClickPathItem(item.path)">
                        {{ item.label }}
                    </el-breadcrumb-item>
                </el-breadcrumb>
            </div>
            <div class="header-right">
                <transition name="slide-horizontal" @after-enter="header.onSearchInputFocus()" @after-leave="header.onSearchInputStatus(false)">
                    <el-input v-if="header.isSearch == true" ref="searchRef" v-model="header.keyword" placeholder="在当前工作目录中搜索" style="width: 100%;height: 100%;margin-left: 12px"
                              @keydown.enter="header.onSearch()">
                        <template #prefix>
                            <el-icon style="cursor: pointer;" @click="header.onSearch()">
                                <Search/>
                            </el-icon>
                        </template>
                        <template #suffix>
                            <el-icon style="cursor: pointer" @click="header.onSearchInputStatus(undefined)">
                                <Close/>
                            </el-icon>
                        </template>
                    </el-input>
                </transition>
                <i v-if="header.isSearch == false" @click="header.onSearchInputStatus(true)">
                    <svg  xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 1024 1024">
                        <path fill="currentColor" d="m795.904 750.72 124.992 124.928a32 32 0 0 1-45.248 45.248L750.656 795.904a416 416 0 1 1 45.248-45.248zM480 832a352 352 0 1 0 0-704 352 352 0 0 0 0 704"/>
                    </svg>
                </i>
            </div>
        </el-header>

        <el-container class="main-container" v-loading="dirTree.loading" @dblclick="onLRResizeDblClick">
            <!-- 侧边栏 -->
            <transition name="aside-slide">
                <el-aside v-show="header.isDirTree" id="dir_tree_aside" style="--kt-aside-top-height: 42px" :style="`width: ${asideWidth}px`">
                    <div class="tree-container">
                        <el-tree ref="dirTreeRef" :accordion="true"
                                 :data="dirTree.data"
                                 :default-expand-all="false"
                                 :expand-on-click-node="false"
                                 :highlight-current="true"
                                 :node-key=primaryKey
                                 :props="dirTree.dirProps"
                                 :virtual-scroll="true"
                                 style="--el-tree-node-content-height: 35px; width: 100%;"
                                 @current-change="dirTree.onCurrentChange">
                            <template #default="{ node }">
                                <el-image :src="fileIcon(!node.loaded && !node.childNodes.length? 'folder-question':node.expanded && node.childNodes.length > 0? 'folder-open':'folder')"/>
                                <span>{{ node.label }}</span>
                            </template>
                        </el-tree>
                    </div>
                    <div class="tree-bottom">
                        <el-tooltip :content="statistic.details" placement="top-start">
                            <span>{{statistic.count}}个项目</span>
                        </el-tooltip>
                    </div>
                </el-aside>
            </transition>
            <!--调整左右两侧宽度-->
            <div class="resizer" v-show="header.isDirTree" @mousedown.prevent="onLRResize" />
            <!-- 主内容区 -->
            <el-main @contextmenu.prevent.stop="contextmenu.onOpen" @click.self="fileTable.onRowClick()">
                <el-skeleton :animated="true" :loading="fileTable.loading">
                    <!-- 表格骨架屏 -->
                    <template #template>
                        <el-table :data="skeletonData" :height="'100%'" @contextmenu.prevent.stop>
                            <el-table-column align="center" width="55">
                                <template #default>
                                    <el-skeleton-item variant="text" style="width: 100%;" />
                                </template>
                            </el-table-column>
                            <el-table-column label="名称" min-width="300">
                                <template #default>
                                    <el-skeleton-item variant="text" style="width: 80%;" />
                                </template>
                            </el-table-column>
                            <el-table-column label="修改日期">
                                <template #default>
                                    <el-skeleton-item variant="text" style="width: 60%;" />
                                </template>
                            </el-table-column>
                            <el-table-column label="类型">
                                <template #default>
                                    <el-skeleton-item variant="text" style="width: 40%;" />
                                </template>
                            </el-table-column>
                            <el-table-column label="大小">
                                <template #default>
                                    <el-skeleton-item variant="text" style="width: 50%;" />
                                </template>
                            </el-table-column>
                        </el-table>
                    </template>
                    <!-- 实际表格 -->
                    <template #default>
                        <el-table ref="fileTableRef"
                                  :border="true"
                                  :data="tableData"
                                  :height="'100%'"
                                  :highlight-current-row="true"
                                  :row-class-name="fileTable.onRowClassName"
                                  :row-key="primaryKey"
                                  @selection-change="fileTable.onSelectionChange"
                                  @row-contextmenu="fileTable.onRowContextmenu"
                                  @row-click="fileTable.onRowClick"
                                  @row-dblclick="fileTable.onRowDblClick">
                            <el-table-column align="center" type="selection" width="55"/>
                            <el-table-column label="名称" min-width="300" prop="name">
                                <template #default="scope">
                                    <div v-if="scope.row[primaryKey] === fileTable.editRow?.[primaryKey]" class="edit-filename">
                                        <el-input ref="editRowRef" v-model="fileTable.editRow.filename" clearable placeholder="请输入文件名"/>
                                        <el-button :icon="Check" :plain="true" type="primary" @click.stop="fileTable.onRename(scope.row)"/>
                                        <el-button :icon="Close" :plain="true" type="danger" @click="fileTable.onRename()"/>
                                    </div>
                                    <div v-else style="display: flex; align-items: center">
                                        <el-image :src="fileIcon(scope.row.directory?'folder':scope.row.extension)" style="height: 25px;width: 25px"/>
                                        <span style="margin-left: 10px">{{ scope.row.name }}</span>
                                    </div>
                                </template>
                            </el-table-column>
                            <el-table-column label="修改日期" prop="modifyTime"/>
                            <el-table-column label="类型" prop="extension">
                                <template #default="scope">
                                    {{ scope.row.directory ? '文件夹' : !!scope.row.extension ? scope.row.extension : '文件' }}
                                </template>
                            </el-table-column>
                            <el-table-column label="大小" prop="size">
                                <template #default="scope">
                                    {{ !scope.row.directory ? sizeFormat(scope.row.size) : '' }}
                                </template>
                            </el-table-column>
                        </el-table>
                        <transition name="progress-slide">
                            <div v-show="downloadProgress.visible" class="download-progress-bar"
                                 @mouseenter="downloadProgress.expanded = true"
                                 @mouseleave="downloadProgress.expanded = false">
                                <div class="progress-main">
                                    <el-progress :percentage="downloadProgress.percentage" :stroke-width="14" :show-text="false" status="success"/>
                                    <div class="cancel-btn">
                                        <el-tooltip content="取消下载">
                                            <el-button :icon="Close" circle size="small" @click="downloadProgress.onCancel()"/>
                                        </el-tooltip>
                                    </div>
                                </div>

                                <transition name="progress-expand">
                                    <div v-show="downloadProgress.expanded" class="progress-details">
                                        <div class="filename">
                                            <span>{{ downloadProgress.filename }}</span>
                                        </div>
                                        <div class="stats">
                                            <span>进度: {{ downloadProgress.percentage }}%</span>
                                            <span class="divider">|</span>
                                            <span>剩余: {{ downloadProgress.remaining }}</span>
                                            <span class="divider">|</span>
                                            <span>总数: {{ downloadProgress.total }}</span>
                                        </div>
                                    </div>
                                </transition>
                            </div>
                        </transition>
                    </template>
                </el-skeleton>
                <context-menu v-model="contextmenu.menus" :before-open="contextmenu.onBeforeOpen" :data="contextmenu.data" @click="contextmenu.onClick"/>
            </el-main>
        </el-container>

        <Uploader ref="uploaderRef"/>
    </el-container>
</template>

<!--侧边栏过渡动画-->
<style lang="scss" scoped>
.aside-slide-enter-active {
    animation: asideIn 0.45s cubic-bezier(0.25, 0.8, 0.25, 1); // 更柔和的贝塞尔曲线
}

.aside-slide-leave-active {
    animation: asideOut 0.45s cubic-bezier(0.25, 0.8, 0.25, 1); // 更自然的退出曲线
}

@keyframes asideIn {
    0% {
        transform: translateX(-100%); // 初始位置向左偏移
        opacity: 0; // 开始时透明度为0
    }
    50% {
        opacity: 0.6; // 中途透明度逐渐增加
    }
    100% {
        transform: translateX(0); // 结束位置为正常位置
        opacity: 1; // 结束时透明度为1
    }
}

@keyframes asideOut {
    0% {
        transform: translateX(0); // 初始位置为正常位置
        opacity: 1; // 开始时透明度为1
    }
    50% {
        opacity: 0.6; // 中途透明度逐渐增加
    }
    100% {
        transform: translateX(-100%); // 结束位置向左偏移
        opacity: 0; // 结束时透明度为0
    }
}

/* 性能优化补充 */
.el-aside {
    will-change: transform, opacity; /* 提示浏览器准备变换 */
    backface-visibility: hidden; /* 防止背面可见 */
    contain: strict; /* 限制重绘区域 */
    z-index: 2000;
    transform: translateZ(0); /* 强制启用 GPU 加速 */
}
</style>

<!--调整布局大小-->
<style lang="scss" scoped>
.resizer {
    width: 5px;
    height: 100%;
    background: var(--el-border-color-light);
    cursor: col-resize;
    position: relative;
    z-index: 1;

    &:hover {
        background: var(--el-color-primary);
    }

    &::after {
        content: '';
        position: absolute;
        left: 1px;
        width: 3px;
        height: 100%;
        background: var(--el-color-primary);
        opacity: 0;
        transition: opacity 0.3s;
    }

    &:hover::after {
        opacity: 0.5;
    }
}

.el-aside {
    /* 提升渲染性能 */
    contain: strict;       /*限制浏览器重排范围*/
    will-change: width;    /*提示浏览器提前优化*/
    backface-visibility: hidden; /*避免闪烁*/
}
</style>

<!--下载进度-->
<style lang="scss" scoped>
.progress-slide-enter-active,
.progress-slide-leave-active {
    transition: all 0.6s ease;
}

.progress-slide-enter-from,
.progress-slide-leave-to {
    transform: translateY(100%);
    opacity: 0;
}

.progress-expand-enter-active,
.progress-expand-leave-active {
    transition: all 0.2s ease;
    max-height: 60px;
}

.download-progress-bar {
    padding: 8px;
    background: var(--el-bg-color);
    border-top: 1px solid var(--el-border-color-light);
    box-shadow: var(--el-box-shadow-dark);
    flex-shrink: 0;

    .progress-main {
        display: flex;
        align-items: center;

        :deep(.el-progress) {
            flex: 1;
            margin-right: 12px;
        }

        .cancel-btn {
            flex-shrink: 0;
        }
    }

    .progress-details {
        margin-top: 5px;
        padding: 5px 5px;
        font-size: 12px;
        display: flex;
        align-items: center;
        justify-content: space-between;

        .filename {
            color: var(--el-text-color-primary);
        }

        .stats {
            color: var(--el-text-color-secondary);

            .divider {
                margin: 0 8px;
                opacity: 0.5;
            }
        }
    }


    &:hover {
        transform: translateY(0);
    }
}
.el-main {
    display: flex;
    flex-direction: column;
    height: 100%;

    .el-table {
        flex: 1 1 0%;
        overflow: auto;
    }
}
</style>

<!--基础-->
<style lang="scss" scoped>
.file-container {
    height: 100%;
    padding-top: 2px;
    --header-height: calc(var(--component-size) + 10px);
}

/* 定义过渡样式 */
.slide-horizontal-enter-active {
    transition: transform 0.5s ease-out, opacity 0.5s ease-out; /* 进入时保持平滑 */
}

.slide-horizontal-leave-active {
    transition: transform 0.5s ease-in, opacity 0.5s ease-in; /*离开时速度更慢 */
    /*transition: transform 0.5s cubic-bezier(0.6, 0.04, 0.98, 0.33), opacity 0.5s cubic-bezier(0.6, 0.04, 0.98, 0.33);*/
}

/* 定义进入前和离开后的状态 */
.slide-horizontal-enter-from, .slide-horizontal-leave-to {
    transform: translateX(100%); /* 初始状态：元素位于容器右侧 */
    opacity: 0; /* 配合透明度渐变效果 */
}

/* 定义进入后的状态 */
.slide-horizontal-enter-to {
    transform: translateX(0); /* 最终状态：元素回到正常位置 */
    opacity: 1;
}


.el-header {
    --el-border-radius-base: 20px;
    --button-space: 4px;
    --icon-fill-bg-color:  #dcdfe654;
    background: var(--el-bg-color) !important;
    border-top: 0 solid var(--el-border-color-light);
    border-bottom: 0 solid var(--el-border-color-light);
    padding: 0;
    height: var(--header-height) !important;
    display: flex !important;
    justify-content: space-between;

    .header-left, .header-right {
        height: 100%;
        width: auto;
        display: flex;
        align-items: center;
        justify-content: flex-start;

        i {
            width: var(--component-size);
            height: var(--component-size);
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;

            &:first-child {
                margin-left: var(--button-space);
            }

            &:last-child {
                margin-right: var(--button-space);
            }

            &:hover {
                background-color: var(--icon-fill-bg-color);
                border-radius: 50%;
            }
        }
    }

    .header-right > .el-input {
        padding: 1px var(--button-space) 1px 0;
    }

    .header-center {
        height: calc(100% - 4px);
        flex-grow: 1;
        overflow-y: auto;
        background-color: var(--el-fill-color-blank);
        box-shadow: 0 0 0 1px var(--el-border-color);
        border-radius: var(--el-border-radius-base);

        &__path {
            height: 100%;
            display: flex;
            align-items: center;

            .el-breadcrumb__item {
                cursor: default;
                height: 100%;
            }

            .el-breadcrumb__item:first-child {
                padding-left: 11px;
            }

            .el-breadcrumb__item:last-child {
                padding-right: 11px;
            }

            .el-breadcrumb__item:hover {
                background:var(--icon-fill-bg-color)
            }
        }
    }
}

.main-container {
    height: calc(100% - var(--header-height));
    margin-top: 0 !important;

    .el-aside {
        overflow: visible;
        text-align: center;
        transform: translateZ(0);

        .tree-container {
            height: calc(100% - var(--kt-aside-top-height)); /* 根据顶部区域高度调整 */
            overflow: auto;
            width: 100%;
            display: grid;
            scrollbar-width: none;

            &::-webkit-scrollbar {
                width: 0;
                background: transparent;
            }
        }

        .tree-bottom {
            height: var(--kt-aside-top-height);
            display: flex;
            align-items: center;
            border-top: 1px solid var(--el-border-color-light);
        }

        .el-tree {

            .el-tree-node__content {
                .el-image {
                    position: relative;
                    float: left;
                    width: 25px;
                    height: 25px;
                }

                span {
                    margin-left: 5px;
                }
            }
        }
    }

    .el-main {
        padding: 0;
        text-align: center;

        .el-skeleton{
            height: 100%;
            overflow: hidden;
        }

        .el-table__body {

            .edit-filename {
                display: flex;
                align-items: center;

                .el-input {
                    width: unset;
                    min-width: 50%;
                    margin-right: 12px;
                }

                .el-button {
                    width: 32px;
                }

                .el-button + .el-button {
                    margin-left: 12px;
                }
            }
        }
    }
}

.file-cell {
    padding: 0 !important;
}

</style>
